﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Xtensible.Extensions.Mapping
{
    public class MapAction
    {
        internal Aliases Aliases { get; set; }
        internal DataTypeTriggers DataTypeTriggers { get; set; }
        internal PropertyNameTriggers PropertyNameTriggers { get; set; }
        internal Exclusions Exclusions { get; set; }
        internal Type SourceType { get; set; }
        internal object Source { get; set; }
        internal Dictionary<string, string> FlattenPrefixes { get; set; }
        internal Dictionary<string, List<string>> FlattenExclusions { get; set; }
        internal Func<object, object> FlatteningFunc { get; set; }

        public T Map<T>() where T : new()
        {
            var t = new T();
            return Map(t);
        }

        public T Map<T>(T destination)
        {

            //Perform custom flattening operations first and copy result into destination
            if (FlatteningFunc != null)
            {
                var mapped = FlatteningFunc(Source);
                destination = mapped.Map(destination);
            }

            var properties = Helper.GetProperties<T>();

            foreach (var targetProperty in properties)
            {
                string sourcePreFlatennedProperty;

                if (IsFlattenedProperty(targetProperty, out sourcePreFlatennedProperty))
                {
                    
                    //Ensure that source property was not excluded (although why would they flatten an excluded property?)
                    if (Exclusions == null || !Exclusions.Contains(sourcePreFlatennedProperty))
                    {
                        //Ensure that we don't want to exclude the target property from taking in flatenned value
                        if (FlattenExclusions == null || !FlattenExclusions.ContainsKey(sourcePreFlatennedProperty) || !FlattenExclusions[sourcePreFlatennedProperty].Contains(targetProperty.Name))
                        {
                            PropertyInfo sourceProperty = null;
                            sourceProperty = SourceType.GetProperty(sourcePreFlatennedProperty);

                            //Level 1 object, e.g. Source.Level1
                            var sourceValue = sourceProperty.GetValue(Source, null);

                            if (sourceValue != null)
                            {
                                //Get Level 2 object, e.g. Source.Level1.Level2
                                var childPropertyName =
                                    targetProperty.Name.Substring(FlattenPrefixes[sourcePreFlatennedProperty].Length);
                                var childProperty = sourceValue.GetType().GetProperty(childPropertyName);

                                if (childProperty != null)
                                {
                                    var childPropertyVal = childProperty.GetValue(sourceValue, null);

                                    if (childPropertyVal != null)
                                    {
                                        targetProperty.SetValue(destination, childPropertyVal, null);
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    PropertyInfo sourceProperty = null;
                    sourceProperty = Helper.GetSourceProperty(Aliases, targetProperty, Exclusions, SourceType,
                                                              sourceProperty);
                    if (sourceProperty != null)
                    {
                        //skip indexers
                        if (sourceProperty.GetIndexParameters().Length > 0)
                            continue;

                        var sourceValue = sourceProperty.GetValue(Source, null);
                        var targetValue = sourceValue;

                        //Note: Property name trigger takes precedence over data type trigger.  They are not chained
                        if (PropertyNameTriggers != null && PropertyNameTriggers.Keys.Contains(sourceProperty.Name))
                        {
                            targetValue = PropertyNameTriggers[sourceProperty.Name](sourceValue);
                        }

                        else if (DataTypeTriggers != null && DataTypeTriggers.Keys.Contains(sourceProperty.PropertyType))
                        {
                            targetValue = DataTypeTriggers[sourceProperty.PropertyType](sourceValue);
                        }

                        targetProperty.SetValue(destination, targetValue, null);
                    }
                }
            }


            return destination;
        }

        private bool IsFlattenedProperty(PropertyInfo targetProperty, out string sourceProperty)
        {
            sourceProperty = null;

            if (FlattenPrefixes == null)
                return false;

            var prefix = FlattenPrefixes.Values.FirstOrDefault(fp => targetProperty.Name.StartsWith(fp));

            sourceProperty = FlattenPrefixes.Keys.FirstOrDefault(k => FlattenPrefixes[k] == prefix);

            return sourceProperty != null;
        }
    }
}